﻿// This is an open source non-commercial project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com

// ReSharper disable CheckNamespace
// ReSharper disable CommentTypo
// ReSharper disable IdentifierTypo
// ReSharper disable InconsistentNaming
// ReSharper disable MemberCanBePrivate.Global

/* MemoryEx.cs --
 * Ars Magna project, http://arsmagna.ru
 */

#region Using directives

using System;

using AM.Text;

#endregion

#nullable enable

namespace AM.Linguistics.Hunspell.Infrastructure;

internal static class MemoryEx
{
    public delegate bool SplitPartHandler (ReadOnlySpan<char> part, int index);

    public static int IndexOf (this ReadOnlySpan<char> @this, char value, int startIndex)
    {
        var result = @this.Slice (startIndex).IndexOf (value);
        if (result >= 0)
        {
            result += startIndex;
        }

        return result;
    }

    public static int IndexOfAny (this ReadOnlySpan<char> @this, char value0, char value1, int startIndex)
    {
        var result = @this.Slice (startIndex).IndexOfAny (value0, value1);
        if (result >= 0)
        {
            result += startIndex;
        }

        return result;
    }

    public static int IndexOfAny (this ReadOnlySpan<char> @this, CharacterSet chars)
    {
        for (var searchLocation = 0; searchLocation < @this.Length; searchLocation++)
            if (chars.Contains (@this[searchLocation]))
            {
                return searchLocation;
            }

        return -1;
    }

    public static bool Equals (this ReadOnlySpan<char> @this, string value, StringComparison comparison)
    {
        return value != null && @this.Equals (value.AsSpan(), comparison);
    }

    public static bool EqualsOrdinal (this ReadOnlySpan<char> @this, ReadOnlySpan<char> value)
    {
        return @this.SequenceEqual (value);
    }

    public static bool EqualsLimited (this ReadOnlySpan<char> @this, ReadOnlySpan<char> value, int lengthLimit)
    {
        return @this.Limit (lengthLimit).EqualsOrdinal (value.Limit (lengthLimit));
    }

    public static bool ContainsAny (this ReadOnlySpan<char> @this, char value0, char value1)
    {
        return @this.IndexOfAny (value0, value1) >= 0;
    }

    public static bool Contains (this ReadOnlySpan<char> @this, char value)
    {
        return @this.IndexOf (value) >= 0;
    }

    public static bool Contains (this ReadOnlySpan<char> @this, ReadOnlySpan<char> value)
    {
        return @this.IndexOf (value) >= 0;
    }

    public static bool StartsWith (this ReadOnlySpan<char> @this, string value, StringComparison comparison)
    {
#if DEBUG
        if (value == null)
        {
            throw new ArgumentNullException (nameof (value));
        }
#endif
        return @this.StartsWith (value.AsSpan(), comparison);
    }

    public static bool StartsWith (this ReadOnlySpan<char> @this, char value)
    {
        return !@this.IsEmpty && @this[0] == value;
    }

    public static bool EndsWith (this ReadOnlySpan<char> @this, char value)
    {
        return !@this.IsEmpty && @this[@this.Length - 1] == value;
    }

    public static bool Split (this ReadOnlySpan<char> @this, char value0, SplitPartHandler partHandler)
    {
        var partIndex = 0;
        var startIndex = 0;
        int commaIndex;
        int partLength;
        while ((commaIndex = @this.IndexOf (value0, startIndex)) >= 0)
        {
            partLength = commaIndex - startIndex;
            if (partLength > 0)
            {
                if (!partHandler (@this.Slice (startIndex, partLength), partIndex))
                {
                    return false;
                }

                partIndex++;
            }

            startIndex = commaIndex + 1;
        }

        partLength = @this.Length - startIndex;
        return partLength > 0
               && partHandler (@this.Slice (startIndex, partLength), partIndex);
    }

    public static bool Split (this ReadOnlySpan<char> @this, char value0, char value1, SplitPartHandler partHandler)
    {
        var partIndex = 0;
        var startIndex = 0;
        int commaIndex;
        int partLength;
        while ((commaIndex = @this.IndexOfAny (value0, value1, startIndex)) >= 0)
        {
            partLength = commaIndex - startIndex;
            if (partLength > 0)
            {
                if (!partHandler (@this.Slice (startIndex, partLength), partIndex++))
                {
                    return false;
                }
            }

            startIndex = commaIndex + 1;
        }

        partLength = @this.Length - startIndex;
        return partLength > 0
               && partHandler (@this.Slice (startIndex, partLength), partIndex);
    }

    public static bool SplitOnTabOrSpace (this ReadOnlySpan<char> @this, SplitPartHandler partHandler)
    {
        return @this.Split (' ', '\t', partHandler);
    }

    public static string Without (this ReadOnlySpan<char> @this, char value)
    {
        var removeIndex = @this.IndexOf (value);
        if (removeIndex < 0)
        {
            return @this.ToString();
        }

        if (removeIndex == @this.Length - 1)
        {
            return @this.Slice (0, removeIndex).ToString();
        }

        var builder = AM.Text.StringBuilderPool.Shared.Get();
        builder.EnsureCapacity (@this.Length - 1);
        builder.Append (@this.Slice (0, removeIndex));

        for (var i = removeIndex; i < @this.Length; i++)
        {
            ref readonly var c = ref @this[i];
            if (value != c)
            {
                builder.Append (c);
            }
        }

        return builder.ReturnShared();
    }

    public static ReadOnlySpan<char> Replace (this ReadOnlySpan<char> @this, char oldChar, char newChar)
    {
        var replaceIndex = @this.IndexOf (oldChar);
        if (replaceIndex < 0)
        {
            return @this;
        }

        var builder = AM.Text.StringBuilderPool.Shared.Get();
        builder.EnsureCapacity (@this.Length);
        builder.Append (@this.Slice (0, replaceIndex));
        builder.Append (newChar);
        for (var i = replaceIndex + 1; i < @this.Length; i++)
        {
            ref readonly var c = ref @this[i];
            builder.Append (c == oldChar ? newChar : c);
        }

        return builder.ReturnShared().AsSpan();
    }

    public static ReadOnlySpan<char> Replace (this ReadOnlySpan<char> @this, string oldText, string newText)
    {
        var replaceIndex = @this.IndexOf (oldText.AsSpan());
        if (replaceIndex < 0)
        {
            return @this;
        }

        // TODO: use replaceIndex to optimize

        return @this.ToString().Replace (oldText, newText).AsSpan();
    }

    public static ReadOnlySpan<char> Reversed (this ReadOnlySpan<char> @this)
    {
        if (@this.Length <= 1)
        {
            return @this;
        }

        var chars = new char[@this.Length];
        var lastIndex = @this.Length - 1;
        for (var i = 0; i < chars.Length; i++) chars[i] = @this[lastIndex - i];

        return new ReadOnlySpan<char> (chars);
    }

    public static ReadOnlySpan<char> Limit (this ReadOnlySpan<char> @this, int maxLength)
    {
#if DEBUG
        if (maxLength < 0)
        {
            throw new ArgumentOutOfRangeException (nameof (maxLength));
        }
#endif
        return @this.Length > maxLength ? @this.Slice (0, maxLength) : @this;
    }

    public static string ConcatString (this ReadOnlySpan<char> @this, string value)
    {
#if DEBUG
        if (value == null)
        {
            throw new ArgumentNullException (nameof (value));
        }
#endif
        if (@this.IsEmpty)
        {
            return value;
        }

        if (value.Length == 0)
        {
            return @this.ToString();
        }

        var builder = AM.Text.StringBuilderPool.Shared.Get();
        builder.EnsureCapacity (@this.Length + value.Length);
        builder.Append (@this);
        builder.Append (value);
        return builder.ReturnShared();
    }

    public static string ConcatString (this ReadOnlySpan<char> @this, char value)
    {
        return @this.ConcatString (value.ToString());
    }
}
